package com.itkjb.leisurely.center.filter;


import com.itkjb.leisurely.authentication.core.common.properties.SecurityProperties;
import com.itkjb.leisurely.authentication.core.common.utils.JwtUtil;
import com.itkjb.leisurely.authentication.core.common.utils.Oauth2TokenUtil;
import com.itkjb.leisurely.core.base.BaseResult;
import com.itkjb.leisurely.core.util.HttpResponseUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * Copyright (C), 2018-2099
 * FileName: CustomBasicAuthenticationFilter
 *
 * @Author: lix
 * @Date: 2019-06-23 15:32
 * @Description: 自定义客户端 认证过滤器
 * @Version: V1.0.0
 */
@Slf4j
public class CustomBasicAuthenticationFilter extends OncePerRequestFilter {

    private ClientDetailsService clientDetailsService;

    private PasswordEncoder passwordEncoder;

    SecurityProperties securityProperties;
    /**
     * 认证url
     */
    private static String OAUTH_TOKEN_URL ="/oauth/token";

    /**
     * 授权类型
     */
    private static String GRANT_TYPE_PARAMTER_NAME = "grant_type";


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {


        if (!request.getRequestURI().equals(OAUTH_TOKEN_URL) ||
                (!request.getParameter(GRANT_TYPE_PARAMTER_NAME).equals("password") && !request.getParameter(GRANT_TYPE_PARAMTER_NAME).equals("authorization_code"))) {
            // 如果是web端 刷新token 去除 basic 认证
            boolean doNext = true;
            //如果是刷新token，没有带basic认证
            if(isRefreshTokenWithWebClient(request)){
                Claims claims = null;
                if(StringUtils.isNotBlank(request.getParameter("access_token"))){
                    try {
                        claims = JwtUtil.parseJWT(request.getParameter("access_token"),securityProperties.getToken().getJwtSigningKey());
                    } catch (Exception e) {
                    }
                    if(claims == null){
                        claims = Oauth2TokenUtil.getClaimsFromHeader(request, securityProperties.getToken().getJwtSigningKey());
                    }
                }
                // 如果是 web端 -> webapp  刷新，自动认证
                if(claims !=null && (securityProperties.getClient().getClientId()).equals(claims.get("client_id"))){
                    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                    if (authentication == null || !authentication.isAuthenticated()) {
                        UsernamePasswordAuthenticationToken token =
                                new UsernamePasswordAuthenticationToken(securityProperties.getClient().getClientId(), securityProperties.getClient().getClientSecurity(), null);
                        SecurityContextHolder.getContext().setAuthentication(token);
                        log.debug(claims.get("user_name").toString()+" 通过webapp 刷新token");
                    }
                }else{
                    doNext = false;
                }
            }
            if(doNext){
                filterChain.doFilter(request, response);
            }else{
                HttpResponseUtil.writeResultWithErrorBaseResult(response,null,"登陆已过期，请重新登录!");
            }
            return;
        }

        String[] clientDetails = null;
        try {
            clientDetails = this.isHasClientDetails(request);
        } catch (Exception e) {
           log.error("解析请求头中的 Authorization 失败", e);
        }
        if (clientDetails == null) {
            // BaseResponse bs = HttpResponse.baseResponse(HttpStatus.UNAUTHORIZED.value(), "请求中未包含客户端信息")
            log.error("请求中未包含客户端信息");
            responseErrorMsg(response);
            return;
        }
        this.handle(request, response, clientDetails, filterChain);
    }

    /**
     * 判断是否是刷新token，没有带basic 认证
     * @param request
     * @return
     */
    protected  boolean isRefreshTokenWithWebClient(HttpServletRequest request){
        boolean bln =false;
        // 首先是刷新token类型
        String[] hasClientDetails = null;
        try {
            hasClientDetails = isHasClientDetails(request);
        } catch (Exception e) {
        }
        if(hasClientDetails ==null){
            bln = true;
        }
        return bln;
    }

    private void responseErrorMsg(HttpServletResponse response) throws  IOException{
        BaseResult res  = new BaseResult();
        res.setMsg("客户端校验认证失败");
        Map<String,String> resmap = new HashMap<>(5);
        resmap.put("code", HttpStatus.UNAUTHORIZED.value()+"");
        resmap.put("msg",res.getMsg());
        resmap.put("error","invalid_client");
        resmap.put("error_description","校验认证失败");
        resmap.put("body", "");
        log.debug("clientDetails is required");

        HttpResponseUtil.writeResult(response, resmap, HttpStatus.OK);

    }
    private void handle(HttpServletRequest request, HttpServletResponse response, String[] clientDetails, FilterChain filterChain) throws IOException, ServletException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication != null && authentication.isAuthenticated()) {
            filterChain.doFilter(request, response);
            return;
        }


        BaseClientDetails details = (BaseClientDetails) this.clientDetailsService.loadClientByClientId(clientDetails[0]);

        if(details ==null){
            log.error("client details not find");
            responseErrorMsg(response);
            return;
        }
        if(!passwordEncoder.matches(clientDetails[1],details.getClientSecret())){
            log.error("client password not match");
            responseErrorMsg(response);
            return;

        }
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(details.getClientId(), details.getClientSecret(), details.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(token);


        filterChain.doFilter(request, response);
    }

    // 判断请求头中是否包含client信息，不包含返回false
    private String[] isHasClientDetails(HttpServletRequest request) throws  Exception{

        String[] params = null;

        String header = request.getHeader(HttpHeaders.AUTHORIZATION);

        if (header != null) {

            String basic = header.substring(0, 5);

            if (basic.toLowerCase().contains("basic")) {

                String tmp = header.substring(6);
                String defaultClientDetails = new String(Base64.getDecoder().decode(tmp));

                String[] clientArrays = defaultClientDetails.split(":");

                if (clientArrays.length != 2) {
                    return params;
                } else {
                    params = clientArrays;
                }

            }
        }

        String id = request.getParameter("client_id");
        String secret = request.getParameter("client_secret");

        if (header == null && id != null) {
            params = new String[]{id, secret};
        }


        return params;
    }

    public ClientDetailsService getClientDetailsService() {
        return clientDetailsService;
    }

    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }


    public PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
}
